Skip to content

Ensure await on storeEntity and handle async operations correctly - #171

Open
bitbacchus wants to merge 7 commits into
mainfrom
await-and-call
Open

Ensure await on storeEntity and handle async operations correctly#171
bitbacchus wants to merge 7 commits into
mainfrom
await-and-call

Conversation

@bitbacchus

Copy link
Copy Markdown
Member

Fixes six places where an async call's promise was never awaited (or a rejection never caught), so failures were silently swallowed and ordering wasn't guaranteed under load. Distinct from the ask-timeout resilience work — this is about unawaited writes/sends, not timed-out asks.

Backend — unawaited storeEntity (caller got a success reply before Mongo confirmed; a write error or restart mid-write was silently lost):

  • SessionStore.StoreSession — session lost on write failure/restart → silent logout. Also un-nests the async-inside-immer-create() pattern.
  • FingerprintStore (Block/Unblock/IncreaseCount/StoreFingerprint) — a blocked fingerprint could revert to unblocked after a restart.
  • StatisticsActor — answer stats were broadcast to subscribers before persistence.
  • UserStore role sync — session kept stale permissions in a window after a role change; now runs after storeEntity, using ask.

Frontend:

  • CurrentQuizActor.DeleteQuestion — await this.send was a no-op (send returns void), so a question could be deleted before QuizActor applied the groups update → dangling group refs. Now uses ask.
  • ChangeNicknameModal + SharingActor.AddEntry — .ask().then() with no .catch; a UserStore rejection was silently lost. Now surfaced to the UI.

Rebased onto main (v1.7.1); typecheck passes across all packages.

claude added 6 commits August 20, 2026 13:56
DeleteQuestion sent the groups patch with fire-and-forget `send`
(the `await` was a no-op on void), so the question could be
removed from the QuestionActor before QuizActor had processed the
groups update, leaving dangling group references under load.

https://claude.ai/code/session_01DUUFUFpsvj4y6xU5172pX4
storeEntity was fire-and-forget: the MongoDB write could fail
silently while the caller already received a success reply.
On any write error or process restart the session was lost,
effectively logging the user out with no warning.

Also refactors the async-inside-create pattern out: getEntity
is now awaited before create(), and create() is synchronous,
which avoids nested create() races introduced by storeEntity's
own internal cache update.

https://claude.ai/code/session_01DUUFUFpsvj4y6xU5172pX4
The ask+send block inside a synchronous create() callback was
fire-and-forget: the admin got a success reply before the
session role was updated, leaving a window where a downgraded
user still held their old permissions.

Moves the session update after storeEntity completes, uses ask
for StoreSession (now that StoreSession awaits its own write),
and guards against users with no active session.

https://claude.ai/code/session_01DUUFUFpsvj4y6xU5172pX4
Block, Unblock, IncreaseCount, and StoreFingerprint all called
storeEntity without await inside synchronous .match() callbacks.
A MongoDB write failure was silently discarded, so a blocked
fingerprint could revert to unblocked after a process restart.

Restructures each handler to extract the fingerprint via
early-return rather than .match(), allowing storeEntity to be
properly awaited before notifying subscribers.

https://claude.ai/code/session_01DUUFUFpsvj4y6xU5172pX4
…csActor

Quiz answer statistics were broadcast to all subscribers and
returned to the caller before the MongoDB write completed.
A write failure was silently swallowed, leaving subscribers
with data that was never persisted.

https://claude.ai/code/session_01DUUFUFpsvj4y6xU5172pX4
ChangeNicknameModal: .ask().then() had no .catch(), so a
UserStore network error propagated as an unhandled rejection
and the uniqueness error was silently lost.

SharingActor.AddEntry: same pattern — a UserStore rejection
on teacher lookup left the sharing dialog with no feedback.
Now maps the rejection to a queryNotFound error entry so the
UI shows an explicit failure rather than silently doing nothing.

https://claude.ai/code/session_01DUUFUFpsvj4y6xU5172pX4
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

@bitbacchus

Copy link
Copy Markdown
Member Author

Prerequisites before merging

  1. Add strategy = "Resume" (or a local try/catch) to SessionStore and
    FingerprintStore so an awaited-write failure doesn't kill them.
  2. Add backend tests for the session / fingerprint / stats paths.
  3. Add a .catch to the new DeleteQuestion ask (and the nickname/sharing
    asks) — subject to the ts-actors 5 s string-reject-on-timeout
    (ts-actors upstream issues Statistikseite frageformulierungen werden am ende abgeschnitten #1).

… rejection

Add prerequisites to make the await-and-call resilience fixes merge-ready:
- Backend tests for the awaited storeEntity paths: SessionStore and
  FingerprintStore persist before the ask resolves, and a failed write is
  surfaced to the error receiver instead of being swallowed; Block persists
  blocked:true so it can't revert after a restart.
- Wrap CurrentQuizActor.DeleteQuestion in try/catch so a failed groups update
  surfaces (the sequential await already aborts the delete); matches AddQuestion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants